package com.didd.core.sensitive.core;

import cn.hutool.core.util.StrUtil;
import com.didd.core.sensitive.annotation.Sensitive;
import com.didd.core.sensitive.strategy.IStrategy;
import com.didd.core.sensitive.util.SensitiveInfoPatternUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 实体类属性映射工具类 <br>
 * （集成的服务中需要对EntityDictService进行实现，推荐实现时继承BaseEntityDictService）
 * 基本数据类型不支持映射
 * 并支持对元素为对象的list、map进行映射
 *
 * @author diddd
 * @date: 2021/6/24 14:22
 */
@Component
public class SensitiveProcessor {

    private static final Logger log = LoggerFactory.getLogger(SensitiveProcessor.class);

    //private IStrategy iStrategy;

    //本地缓存
    //private ConcurrentHashMap<String, Map<String, String>> dictCache = new ConcurrentHashMap<>();

    public void sensitiveMapping(Object obj) {
        if (obj.getClass().isPrimitive()) {
            return;
        }
        //判断是否list
        if (obj instanceof List) {
            List<Object> list = (List) obj;
            if (CollectionUtils.isEmpty(list)) {
                return;
            }
            list.forEach(y -> {
                if (y != null) {
                    //递归调用
                    sensitiveMapping(y);
                }
            });
        }
        //判断是否map
        else if (obj instanceof Map) {
            Map<Object, Object> map = (Map) obj;
            map.values().forEach(y -> {
                if (y != null) {
                    //递归调用
                    sensitiveMapping(y);
                }
            });
        }
        //普通类型
        else {
            Class<?> aClass = obj.getClass();
            try {
                //获得某个类的所有声明的字段，即包括public、private和proteced，但是不包括父类的申明字段。
                Field[] fields = getAllFields(aClass);
                if (fields.length == 0) {
                    return;
                }
                Arrays.stream(fields).forEach(x -> {
                    processField(obj, aClass, x);
                });
            } catch (Exception e) {
                log.error("对象无法进行字段脱敏:" + aClass.toString(), e);
            }
        }
    }

    /**
     * @param obj
     * @param aClass
     * @param x
     */
    private void processField(Object obj, Class<?> aClass, Field x) {
        //通过反射获取执行的方法
        Object fieldObj = invokeGetMethod(obj, x.getName());
        //获取执行的注解
        Sensitive sensitive = x.getAnnotation(Sensitive.class);

        //如果方法为list
        if (fieldObj instanceof List) {
            List<Object> list = (List) fieldObj;
            if (CollectionUtils.isEmpty(list)) {
                return;
            }
            list.forEach(y -> {
                if (y != null) {
                    //递归执行
                    sensitiveMapping(y);
                }
            });
        } else if (fieldObj instanceof Map) {
            Map<Object, Object> map = (Map) fieldObj;
            map.values().forEach(y -> {
                if (y != null) {
                    //递归执行
                    sensitiveMapping(y);
                }
            });
        } else if (sensitive != null) {
            //获取映射原对象
            try {

                String pattern = sensitive.pattern();
                String replace = sensitive.replaceChar();

                String sourValue = (String) fieldObj;

                if (StrUtil.isNotBlank(pattern)
                        && StrUtil.isNotBlank(replace)) {

                    sourValue = SensitiveInfoPatternUtils.replace((String) fieldObj, pattern, replace);

                } else {
                    IStrategy iStrategy = sensitive.strategy().newInstance();
                    //Object sourceValue = String.valueOf(invokeGetMethod(obj, source));
                    sourValue = iStrategy.desensitization((String) fieldObj, sensitive.begin(), sensitive.end());
                }

                String targetMehtodName = "set" + x.getName().substring(0, 1).toUpperCase()
                        + x.getName().substring(1, x.getName().length());
                Method targetMehtod = aClass.getMethod(targetMehtodName, String.class);

                //设置赋值
                targetMehtod.invoke(obj, sourValue);

            } catch (NoSuchMethodException e) {
                log.error("字段映射出现异常,无法找到对应的方法", e);
            } catch (IllegalAccessException e) {
                log.error("字段映射出现异常,反射调用时出现异常", e);
            } catch (InvocationTargetException e) {
                log.error("字段映射出现异常,赋值反射调用时出现异常", e);
            } catch (InstantiationException e) {
                log.error("字段映射出现异常,反射调用时出现异常", e);
            }
        } else if (fieldObj != null && !fieldObj.getClass().isPrimitive() && !(fieldObj instanceof String)) {
            sensitiveMapping(fieldObj);
        }
    }

    /**
     * 执行某个Field的getField方法
     *
     * @param owner     类
     * @param fieldName 类的属性名称
     * @return
     */
    public static Object invokeGetMethod(Object owner, String fieldName) {
        Class<? extends Object> ownerClass = owner.getClass();

        //fieldName -> FieldName
        String methodName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

        Method method = null;
        try {
            method = ownerClass.getMethod("get" + methodName);
        } catch (SecurityException e) {
            return "";
        } catch (NoSuchMethodException e) {
            return "";
        }
        //invoke getMethod
        try {
            return method.invoke(owner);
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * 获得类的所有声明的字段，即包括public、private和proteced
     * 通过循环获取父类的字段
     *
     * @param clazz
     * @return
     */
    public static Field[] getAllFields(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        return fieldList.toArray(fields);
    }

}
